/*
 * Ethan Levine
 * 
 * The contents of this file are subject to the GNU General Public
 * License (GPL) v2.  The full contents of this license can be
 * found at Mathma's website, http://www.codeplex.com/mathma/license
 */

package mathma.core.calc;

import mathma.core.expr.Variable;
import mathma.core.expr.Expression;
import mathma.core.expr.ExpressionType;
import mathma.core.expr.VariableMapping;

import mathma.core.form.FormulaicExpression;
import mathma.core.form.SuspendedExpression;
import mathma.core.form.UnknownSuspensionException;

import mathma.core.real.Real;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>Represents a derivative.  A derivative can be viewed as a function that gives
 * the slope of another function at any point.</p>
 *
 * <p>Mathematically, this represents the expression d(operand)/d(var).</p>
 *
 * <p>DerivativeExpressions are suspended, and thus require work before a
 * value can be obtained.  For more information about this process, see
 * {@link mathma.core.form.SuspendedExpression}.</p>
 * 
 * @since 0.1.4
 * @author Ethan Levine
 */
public class DerivativeExpression extends SuspendedExpression<Real, Real> {

    private Expression<Real> operand;
    private Variable<Real> var;

    /**
     * Creates a new DerivativeExpression using the given arguments.
     *
     * @param operand The expression to differentiate.
     * @param var The variable to differentiate by.
     */
    public DerivativeExpression(Expression<Real> operand, Variable<Real> var) {
        super(Real.Type);
        this.operand = operand;
        this.var = var;
    }

    /**
     * Returns the expression that will be differentiated.
     * 
     * @return The derivative body.
     */
    public Expression<Real> getOperand() {
        return operand;
    }

    /**
     * Returns the variable that the body of this expression will be differentiated
     * by.
     *
     * @return The variable to differentiate by.
     */
    public Variable<Real> getVariable() {
        return var;
    }
    
    @Override
    public Expression<Real> calculate() throws UnknownSuspensionException {
        Expression<Real> derivative = operand.differentiate(var);
        if (derivative.getExpressionType().equals(ExpressionType.FORMULAIC) && ((FormulaicExpression) derivative).containsSuspended()) {
            derivative = ((FormulaicExpression) derivative).calculate();
        }
        return derivative;
    }
    
    
    public DerivativeExpression newInstance(List<Expression<Real>> operands) {
        if (operands.size() == 2 &&
                Variable.class.isAssignableFrom(operands.get(1).getClass())) {
            return new DerivativeExpression(operands.get(0),
                    (Variable<Real>) operands.get(1));
        } else {
            throw new IllegalArgumentException(
                    "Improper arguments to DerivativeExpression." +
                    "newInstance(List<Expression<Value>>)");
        }
    }
    
    public List<Expression<Real>> getOperands() {
        List<Expression<Real>> operands = new ArrayList<Expression<Real>>();
        operands.add(operand);
        operands.add(var);
        return operands;
    }

    @Override
    public Expression<Real> differentiate(Variable<Real> var) {
        try {
            return calculate().differentiate(var);
        } catch (UnknownSuspensionException e) {
            return new DerivativeExpression(this, var);
        }
    }
}
